/**
 * ﻿Copyright (C) 2007 - 2016 52°North Initiative for Geospatial Open Source
 * Software GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.n52.wps.client;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;

import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactoryConfigurationError;

import net.opengis.ows.x11.ExceptionReportDocument.ExceptionReport;
import net.opengis.wps.x100.DocumentOutputDefinitionType;
import net.opengis.wps.x100.ExecuteDocument;
import net.opengis.wps.x100.ExecuteResponseDocument;
import net.opengis.wps.x100.OutputDataType;
import net.opengis.wps.x100.OutputDescriptionType;
import net.opengis.wps.x100.ProcessDescriptionType;

import org.n52.wps.commons.XMLUtil;
import org.n52.wps.io.IParser;
import org.n52.wps.io.data.IData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/*
 * 
 * returns the processOutputs according to the encoding of the process.
 * @author foerster
 *
 */
public class ExecuteResponseAnalyser {
	
	private static Logger LOGGER = LoggerFactory.getLogger(ExecuteResponseAnalyser.class);
	
	ProcessDescriptionType processDesc;
	ExecuteDocument exec;
	Object response;
	
	public ExecuteResponseAnalyser(ExecuteDocument exec, Object response, ProcessDescriptionType processDesc) throws WPSClientException {
		this.processDesc = processDesc;
		this.exec= exec;
		if(response instanceof ExceptionReport){
			throw new WPSClientException("Output is not ComplexData but an Exception Report");
		}
		this.response = response;
	}
	
	
/**
 * delivers the parsed ComplexData by name
 * @param outputID id of the output element
 * @param binding Class of the output binding, e.g. GenericFileDataBinding
 * @return the parsed ComplexData in the requested format defined by the binding
 * @throws WPSClientException
 */
	public IData getComplexData(String outputID, Class<?> binding) throws WPSClientException {
		return parseProcessOutput(outputID, binding);
		
	}
	
	/**
	 * delivers the parsed ComplexData by index
	 * @param index index of the output element starting with 0
	 * @param binding Class of the output binding, e.g. GenericFileDataBinding
	 * @return the parsed ComplexData in the requested format defined by the binding
	 * @throws WPSClientException
	 */
	public IData getComplexDataByIndex(int index, Class<?> binding) throws WPSClientException {
		ExecuteResponseDocument doc = null;
		if(response instanceof ExecuteResponseDocument){
			doc = (ExecuteResponseDocument) response;
		}else{
			throw new WPSClientException("Output cannot be determined by index since it is either raw data or an exception report");
		}
		OutputDataType[] outputs = doc.getExecuteResponse().getProcessOutputs().getOutputArray();
		int counter = 0; 
		for(OutputDataType output : outputs) {
			if(output.getData().getComplexData() != null) {
				if(counter == index) {
					return this.parseProcessOutput(output.getIdentifier().getStringValue(), binding);
				}
				counter ++;
			}
		}
		return null;
	}
	
	/**
	 * delivers just the URL of a referenced output identified by index
	 * @param index index of the output
	 * @return URL of the stored output
	 * @throws WPSClientException 
	 */
	public String getComplexReferenceByIndex(int index) throws WPSClientException {
		ExecuteResponseDocument doc = null;
		if(response instanceof ExecuteResponseDocument){
			doc = (ExecuteResponseDocument) response;
		}else{
			throw new WPSClientException("Output cannot be determined by index since it is either raw data or an exception report");
		}
		OutputDataType[] outputs = doc.getExecuteResponse().getProcessOutputs().getOutputArray();
		int counter = 0; 
		for(OutputDataType output : outputs) {
			if(output.getReference() != null) {
				if(counter == index) {
					return output.getReference().getHref();
				}
				counter ++;
			}
		}
		RuntimeException rte = new RuntimeException("No reference found in response");		
		LOGGER.error(rte.getMessage());
		throw rte;
	}
		
	
		
	
	/**
	 * parses a specific WPS output
	 * 
	 * @param outputID id of the output
	 * @param outputDataBindingClass class of the desired output binding
	 * @return parsed WPS output as IData
	 * @throws WPSClientException
	 */
	private IData parseProcessOutput(String outputID, Class<?> outputDataBindingClass) throws WPSClientException {
		OutputDescriptionType outputDesc = null;
		
		String schema = null;
		String mimeType = null;
		String encoding = null;
		
		if(exec.getExecute().isSetResponseForm() && exec.getExecute().getResponseForm().isSetRawDataOutput()){
			// get data specification from request
			schema = exec.getExecute().getResponseForm().getRawDataOutput().getSchema();
			mimeType = exec.getExecute().getResponseForm().getRawDataOutput().getMimeType();
			encoding = exec.getExecute().getResponseForm().getRawDataOutput().getEncoding();
		}else if(exec.getExecute().isSetResponseForm() && exec.getExecute().getResponseForm().isSetResponseDocument()){
			DocumentOutputDefinitionType[] outputs = exec.getExecute().getResponseForm().getResponseDocument().getOutputArray();
			for(DocumentOutputDefinitionType output : outputs){
				if(output.getIdentifier().getStringValue().equals(outputID)){
					schema = output.getSchema();
					mimeType = output.getMimeType();
					encoding = output.getEncoding();
				}
			}
			
		}
		
		if(mimeType==null){
			for(OutputDescriptionType tempDesc : processDesc.getProcessOutputs().getOutputArray()) {
				if(outputID.equals(tempDesc.getIdentifier().getStringValue())) {
					outputDesc = tempDesc;
					break;
				}
			}
			// get default data spec since mime type is not set
			mimeType = outputDesc.getComplexOutput().getDefault().getFormat().getMimeType();
			encoding = outputDesc.getComplexOutput().getDefault().getFormat().getEncoding();
			schema = outputDesc.getComplexOutput().getDefault().getFormat().getSchema();
		}
		
		IParser parser = StaticDataHandlerRepository.getParserFactory().getParser(schema, mimeType, encoding, outputDataBindingClass);
		InputStream is = null;
		if(response instanceof InputStream){
			is = (InputStream)response;
		}else if (response instanceof ExecuteResponseDocument){
			ExecuteResponseDocument responseDocument = (ExecuteResponseDocument) response;
			OutputDataType[] processOutputs = responseDocument.getExecuteResponse().getProcessOutputs().getOutputArray();
			for(OutputDataType processOutput : processOutputs){
				if(processOutput.getIdentifier().getStringValue().equalsIgnoreCase(outputID)){
					if(processOutput.isSetReference()){
						//request the reference
						String urlString = processOutput.getReference().getHref();
						URL url;
						try {
							url = new URL(urlString);
							is = url.openStream();
						} catch (MalformedURLException e) {
							throw new WPSClientException("Could not fetch response from referenced URL", e);
						} catch (IOException e) {
							throw new WPSClientException("Could not fetch response from referenced URL", e);
						}
						
					}else{
						String complexDataContent;
						try {
							
							NodeList candidateNodes = processOutput.getData().getComplexData().getDomNode().getChildNodes();
							
						    Node complexDataNode = candidateNodes.getLength() > 1 ? candidateNodes.item(1) : candidateNodes.item(0);							
							
							complexDataContent = XMLUtil.nodeToString(complexDataNode);
							is = new ByteArrayInputStream(complexDataContent.getBytes());
						} catch (TransformerFactoryConfigurationError e) {
							LOGGER.error(e.getMessage());
						} catch (TransformerException e) {
							LOGGER.error(e.getMessage());
						}
					}
					
				}
			}
			
		}else{
			throw new WPSClientException("Wrong output type");
		}
		
		
		if(parser != null) {
			if(encoding != null && encoding.equalsIgnoreCase("base64")){
				return parser.parseBase64(is, mimeType, schema);
			}else{
				return parser.parse(is, mimeType, schema);
			}
		}
		RuntimeException rte = new RuntimeException("Could not find suitable parser");		
		LOGGER.error(rte.getMessage());
		throw rte;
	}
}